#include <cstdlib>
#include <iostream>

#include "Framework/Win32/Win32Framework.h"
#include "Framework/App.h"

#include "GfxDriver/GfxDriver.h"
#include "GfxDriver/Shader.h"
#include "GfxDriver/Texture.h"
#include "GfxDriver/VertexBuffer.h"
#include "GfxDriver/IndexBuffer.h"
#include "GfxDriver/VertexInput.h"
#include "GfxDriver/RenderTarget.h"

#include "Image/ImageService.h"
#include "Image/Image.h"

namespace Orca {

	//--------------------------------------------------------------------------
	class TGfxDriverTestApp : public IApp {

		VertexProgram		*mVProg;
		FragmentProgram	*mFProg;
		Shader				*mShader;
		Texture2d			*mTex2D;
		TextureCube		*mTexCube;
		VertexBuffer		*mVertVB, *mClrVB;
		IndexBuffer		*mIB;
		VertexInput		*mVertInput;
		RenderTarget		*mRendTarget;
		DepthBuffer		*mDepthTarget;
		TextureRendered	*mColorTarget;

		uint32				mVpW, mVpH;

		struct Vertex {
			float x, y, z;
			float u, v;

			void set(float inX, float inY, float inZ, float inU, float inV) {
				x = inX; y = inY; z = inZ; u = inU; v = inV;
			}
		};

	public:

		//------------------------------
		TGfxDriverTestApp() {

		}

		//------------------------------
		virtual ~TGfxDriverTestApp() {

		}

		//------------------------------
		virtual void getAppTitle(Str &title) {
			title = "GfxDriverTest";
		}

		//------------------------------
		virtual void getInitRes(uint32 &width, uint32 &height) {
			width = 640;
			height = 480;
		}

		//------------------------------
		virtual void registerPluginModules() {

		}

		//------------------------------
		virtual bool startup(Framework *framework) {

			INST<GfxDriver>()->setCullMode(CULL_CCW);
			INST<GfxDriver>()->setDepthTestEnable(true);
			INST<GfxDriver>()->setDepthTestFunc(CMP_LESS_EQUAL);

			_create2DTexture();

			_createCubeTexture();
			
			_createVertexAndIndexBuffers();

			_createVertexInput();

			_createShader(framework);

			_createRenderTarget();

			return true;
		}

		//------------------------------
		virtual void shutdown() {

			delete mColorTarget;
			delete mDepthTarget;
			delete mRendTarget;
			delete mVertInput;
			delete mIB;
			delete mClrVB;
			delete mVertVB;
			delete mTexCube;
			delete mTex2D;
			delete mShader;
			delete mVProg;
			delete mFProg;

		}

		//------------------------------
		virtual void step(float dt) {

			GfxDriver *gfxDriver = INST<GfxDriver>();

			
			gfxDriver->beginRender();

			// view matrix should be inverse transform, i.e. the camera is at (0, 0, 10)
			static int rot = 0;
			
			mtx44 viewMtx = translate(0.0f, -5.0f, -10.0f);
			mtx44 worldMtx = rotateY(rot++ * 0.005f);
			
			gfxDriver->setWorldTransform(worldMtx);
			gfxDriver->setViewTransform(viewMtx);

			{
				gfxDriver->bindRenderTarget(mRendTarget);
				gfxDriver->setViewport(0, 0, 512, 512);

				gfxDriver->clearFrameBuf( vec4(1.0f, 1.0f, 1.0f, 1.0f) );
				gfxDriver->clearDepthBuf(1.0f);

				mShader->beginBinding();

				mShader->bindTexture("tex0", mTex2D);

				mShader->endBinding();

				if(mShader) mShader->beginRender();

				if(mVertVB && mClrVB) {

					gfxDriver->beginDrawing(mVertInput);

					gfxDriver->drawIndexed(PT_TRIANGLE_LIST, 0, 12);

					gfxDriver->endDrawing();
				}

				if(mShader) mShader->endRender();
			}

			{
				gfxDriver->bindRenderTarget(NULL);
				gfxDriver->setViewport(0, 0, mVpW, mVpH);

				gfxDriver->clearFrameBuf( vec4(0.05f, 0.1f, 0.2f, 0.0f) );
				gfxDriver->clearDepthBuf(1.0f);

				mShader->beginBinding();

				mShader->bindTexture("tex0", mColorTarget);

				mShader->endBinding();

				if(mShader) mShader->beginRender();

				if(mVertVB && mClrVB) {

					gfxDriver->beginDrawing(mVertInput);

					gfxDriver->drawIndexed(PT_TRIANGLE_LIST, 0, 12);

					gfxDriver->endDrawing();
				}

				if(mShader) mShader->endRender();

			}


			gfxDriver->endRender();
		}

		//------------------------------
		virtual void notifyResChanged(uint32 width, uint32 height) {

			mVpW = width;
			mVpH = height;

			const float zNear = 0.1f;
			const float zFar = 1000.0f;
			const float aspect = (float)width / (float)height;
			const float fovy = gmtl::Math::PI_OVER_4;

			mtx44 projMtxP = identity4();

			float f = 1.0f / tanf( fovy * 0.5f );
			float d = 1.0f / (zNear - zFar);

			projMtxP[0][0] = f / aspect;
			projMtxP[1][1] = f;
			projMtxP[2][2] = (zFar + zNear) * d;
			projMtxP[2][3] = (2.0f * zFar * zNear) * d;
			projMtxP[3][2] = -1.0f;

			INST<GfxDriver>()->setProjTransform(projMtxP);
			INST<GfxDriver>()->setViewport(0, 0, width, height);
		}

		//------------------------------
		virtual void notifyKeyboardEvent(uint8 c) {

		}

		//------------------------------
		virtual void notifyMouseButtonEvent(MOUSE_BUTTON btn, bool down, int x, int y) {

		}

		//------------------------------
		virtual void notifyMouseMotionEvent(int x, int y) {

		}

		//------------------------------
		void _create2DTexture() {

			/*
//#define USE_FLOAT_TEX

#ifdef USE_FLOAT_TEX
	typedef float TTexelType;
	#define _FULL 1.0
	#define _HALF 0.5
	#define _PIXELFORMAT PF_FLOAT32_ABGR
#else
	typedef uint8 TTexelType;
	#define _FULL 255
	#define _HALF 128
	#define _PIXELFORMAT PF_INT8_RGBA
#endif		
			mTex2D = INST<GfxDriver>()->create2DTexture(64, 64, _PIXELFORMAT);
			
			if(mTex2D) {

				TTexelType *dataPtr = (TTexelType*)mTex2D->requestUpload();

				for(int y = 0; y < 64; ++y) {
					for(int x = 0; x < 64; ++x) {

						int i = y * 64 + x;

						TTexelType val = _FULL;

						if( x >= 32 && y < 32 ) val = _HALF;
						if( x < 32 && y >= 32) val = _HALF;

						dataPtr[i*4 + 0] = val;
						dataPtr[i*4 + 1] = val;
						dataPtr[i*4 + 2] = val;
						dataPtr[i*4 + 3] = _FULL;
					}
				}

				mTex2D->uploadToGPU(false);

				printf("texture 2d created!\n");
			}

			#undef _FULL
			#undef _HALF
			*/

			Image *img = INST<ImageService>()->createImageFromFile(
				"GfxDriverTest/texture/teapot.bmp");

			mTex2D = img->create2DTexture(false);

			delete img;

		}

		//------------------------------
		void _createCubeTexture() {

			int fullSize = 64;
			int halfSize = 32;

			mTexCube = INST<GfxDriver>()->createCubeTexture( (uint32)fullSize, PF_INT8_RGBA);
			
			if(mTexCube) {

				for(uint32 side = 0; side < 6; ++side) {
				
					uint8 *dataPtr = (uint8*)mTexCube->requestUpload( (CubeMapSide)side );

					uint32 idx = 0;

					for(int y = 0; y < fullSize; ++y) {
						for(int x = 0; x < fullSize; ++x) {

							vec3 dir;

							float u = (float)x - halfSize + 0.5f;
							float v = (float)y - halfSize + 0.5f;
							
							switch(side) {
								case CUBE_POSITIVE_X:
								{
									dir.set( (float)halfSize, -v, -u);
									break;
								}

								case CUBE_NEGATIVE_X:
								{
									dir.set(-(float)halfSize, -v,  u);
									break;
								}

								case CUBE_POSITIVE_Y:
								{
									dir.set( u,  (float)halfSize,  v);
									break;
								}

								case CUBE_NEGATIVE_Y:
								{
									dir.set( u, -(float)halfSize, -v);
									break;
								}

								case CUBE_POSITIVE_Z:
								{
									dir.set( u, -v,  (float)halfSize);
									break;
								}

								case CUBE_NEGATIVE_Z:
								{
									dir.set(-u, -v, -(float)halfSize);
									break;
								}
							}

							normalize(dir);

							dir = (dir * 0.5f) + vec3(0.5f, 0.5f, 0.5f);

							if(dir[0] < 0.0f) dir[0] = 0.0f;
							if(dir[1] < 0.0f) dir[1] = 0.0f;
							if(dir[2] < 0.0f) dir[2] = 0.0f;

							if(dir[0] > 1.0f) dir[0] = 1.0f;
							if(dir[1] > 1.0f) dir[1] = 1.0f;
							if(dir[2] > 1.0f) dir[2] = 1.0f;

							*dataPtr++ = (uint8)(dir[0] * 255.0f);
							*dataPtr++ = (uint8)(dir[1] * 255.0f);
							*dataPtr++ = (uint8)(dir[2] * 255.0f);
							*dataPtr++ = 0xff;
						}
					}

					mTexCube->uploadToGPU(false);

				}

				printf("Texture CUBE created\n");
			}
		}

		//------------------------------
		void _createVertexAndIndexBuffers() {

			// vertex buffers
			mVertVB = INST<GfxDriver>()->createVertexBuffer( sizeof(Vertex) * 8, true );

			/*
			 5 7
			 4 6
			1 3
			0 2
			*/

			if(mVertVB) {

				Vertex* vert = (Vertex*)mVertVB->requestUpload(true);

				if(vert) {

					vert[0].set(-2.0f,-2.0f, 2.0f, 0.0f, 0.0f);
					vert[1].set(-2.0f, 2.0f, 2.0f, 0.0f, 1.0f);
					vert[2].set( 2.0f,-2.0f, 2.0f, 1.0f, 0.0f);
					vert[3].set( 2.0f, 2.0f, 2.0f, 1.0f, 1.0f);

					vert[4].set(-2.0f,-2.0f,-2.0f, 1.0f, 0.0f);
					vert[5].set(-2.0f, 2.0f,-2.0f, 1.0f, 1.0f);
					vert[6].set( 2.0f,-2.0f,-2.0f, 0.0f, 0.0f);
					vert[7].set( 2.0f, 2.0f,-2.0f, 0.0f, 1.0f);

					mVertVB->uploadToGPU();

					printf("position vertex buffer created!\n");
				}
			}

			mClrVB = INST<GfxDriver>()->createVertexBuffer( sizeof(vec3) * 8, true );

			if(mClrVB) {

				vec3* vert = (vec3*)mClrVB->requestUpload(true);

				if(vert) {

					vert[0] = vec3(1.0f, 1.0f, 1.0f); //vec3(1.0f, 0.0f, 0.0f);
					vert[1] = vec3(1.0f, 1.0f, 1.0f); //vec3(0.0f, 1.0f, 0.0f);
					vert[2] = vec3(1.0f, 1.0f, 1.0f); //vec3(0.0f, 0.0f, 1.0f);
					vert[3] = vec3(1.0f, 1.0f, 1.0f); //vec3(1.0f, 1.0f, 1.0f);

					vert[4] = vec3(1.0f, 1.0f, 1.0f); //vec3(1.0f, 0.0f, 0.0f);
					vert[5] = vec3(1.0f, 1.0f, 1.0f); //vec3(0.0f, 1.0f, 0.0f);
					vert[6] = vec3(1.0f, 1.0f, 1.0f); //vec3(0.0f, 0.0f, 1.0f);
					vert[7] = vec3(1.0f, 1.0f, 1.0f); //vec3(1.0f, 1.0f, 1.0f);

					mClrVB->uploadToGPU();

					printf("color vertex buffer created!\n");
				}
			}

			mIB = INST<GfxDriver>()->createIndexBuffer( sizeof(uint16) * 36, true );

			if(mIB) {

				uint16 *idx = (uint16*)mIB->requestUpload(true);

				if(idx) {

					*idx++ = 0; *idx++ = 1; *idx++ = 2;
					*idx++ = 3; *idx++ = 2; *idx++ = 1;

					*idx++ = 4; *idx++ = 6; *idx++ = 5;
					*idx++ = 7; *idx++ = 5; *idx++ = 6;

					*idx++ = 2; *idx++ = 3; *idx++ = 6;
					*idx++ = 7; *idx++ = 6; *idx++ = 3;

					*idx++ = 0; *idx++ = 4; *idx++ = 1;
					*idx++ = 5; *idx++ = 1; *idx++ = 4;

					*idx++ = 1; *idx++ = 5; *idx++ = 3;
					*idx++ = 7; *idx++ = 3; *idx++ = 5;

					*idx++ = 0; *idx++ = 2; *idx++ = 4;
					*idx++ = 6; *idx++ = 4; *idx++ = 2;

					mIB->uploadToGPU();

					printf("index buffer created!\n");
				}
			}
		}
		
		//------------------------------
		void _createVertexInput() {

			mVertInput = INST<GfxDriver>()->createVertexInput();

			mVertInput->append( mVertVB, 0, sizeof(Vertex) ) << VE_XYZ() << VE_TEXCOORD2(0);
			mVertInput->append( mClrVB, 0, sizeof(float) * 3 ) << VE_DIFFUSE_3F();
			mVertInput->append( mIB, 0, IDT_16 );
		}
		
		//------------------------------
		void _createShader(Framework *framework) {

			// shader
			uint8 *vpData; uint32 vpDataLength;
			uint8 *fpData; uint32 fpDataLength;
			
			INST<FileService>()->readFile("GfxDriverTest/shader/Simple_vp.glsl", vpData, vpDataLength);
			INST<FileService>()->readFile("GfxDriverTest/shader/Simple_fp.glsl", fpData, fpDataLength);

			mVProg = INST<GfxDriver>()->createVertexProgram(vpData);
			mFProg = INST<GfxDriver>()->createFragmentProgram(fpData);
			mShader = INST<GfxDriver>()->createShader(mVProg, mFProg);

			if(mShader) {
				
				vec4 fillColors[2] = {
					vec4(1.0f, 0.2f, 0.2f, 1.0f),
					vec4(1.0f, 1.0f, 1.0f, 1.0f),
				};

				mShader->beginBinding();

				mShader->bindVec4Array( "g_FillColor", fillColors, 2 );
				mShader->bindTexture("tex0", mTex2D);
				mShader->bindTexture("tex1", mTexCube);

				mShader->endBinding();

				printf("shader loaded!\n");

			}
		}

		void _createRenderTarget() {

			mRendTarget = INST<GfxDriver>()->createRenderTarget();

			mColorTarget = INST<GfxDriver>()->createRenderedTexture(512, 512, PF_INT8_RGBA);
			mDepthTarget = INST<GfxDriver>()->createDepthBuffer(512, 512, 24, 0);

			mRendTarget->bindColorTarget(0, mColorTarget);
			mRendTarget->bindDepthTarget(mDepthTarget);
		}

		//------------------------------

	};

}

//------------------------------------------------------------------------------
int main(int argc, char **argv) {

	using namespace Orca;

	Framework *fw = new Win32Framework;

	fw->run(new TGfxDriverTestApp, argc, argv);

	return 0;
}

//------------------------------------------------------------------------------

/*
#include <stdlib.h>
#include <stdio.h>

#include <glut.h>
#include <gl/glu.h>

#include <windows.h>

#include "Render/FixedPipelineShader.h"
#include "Render/TriMeshRenderable.h"
#include "Render/TextureManager.h"

#include "Dae/ColladaTriMeshLoader.h"

#include "Scene/Scene.h"
#include "Scene/SceneObject.h"
#include "Scene/TransformController.h"

#ifdef _DEBUG
#	pragma comment(lib, "FColladaSD_MTD")
#else
#	pragma comment(lib, "FColladaSR_MTD")
#endif

//------------------------------------------------------------------------------
bool isKeyPressed(int keyCode) {

	return (GetAsyncKeyState(keyCode) & 0x8000) != 0;
}

//------------------------------------------------------------------------------
static void idle(void);
static void display(void);
static void keyboard(uint8 c, int x, int y);
static void reshape(int width, int height);

//------------------------------------------------------------------------------
using namespace Orca;

Scene scene;
TCamera camera;
TTextureManager texMgr;

typedef std::pair<TTransformable*, ITransformController*> TControllerInst;
typedef std::vector<TControllerInst> TControllerList;
TControllerList controllers;

float mouseSentivity = 0.2f;

//------------------------------------------------------------------------------
enum VIS_FLAGS {

	NORMAL_VIS_FLAG =  (1 << 0),
	REFLECTIVE_VIS_FLAG = (1 << 1),
};

float deg2Rad(float angle) {

	return angle * Math::PI / 180.0f;

}

//------------------------------------------------------------------------------
BEGIN_XFORM_CONTROLLER(TCameraController) {

	static POINT oldPt;

	POINT newPt;
	GetCursorPos(&newPt);

	float dx = (newPt.x - oldPt.x) * mouseSentivity;
	float dy = (newPt.y - oldPt.y) * mouseSentivity;

	if(isKeyPressed(VK_LBUTTON)) {
		target->yaw( deg2Rad(dx) );
		target->pitch( deg2Rad(-dy) );
	}

	if(isKeyPressed(VK_RBUTTON))
		target->moveRel( vec3(0.0f, 0.0f, dy) );

	if(isKeyPressed(VK_MBUTTON))
		target->moveRel( vec3(-dx, dy, 0.0f) );

	oldPt = newPt;
	

} END_XFORM_CONTROLLER();


//------------------------------------------------------------------------------
void initControllers() {

	controllers.push_back( std::make_pair(&camera, new TCameraController) );
}

//------------------------------------------------------------------------------
void deinitControllers() {

	TControllerList::iterator iter = controllers.begin();

	while( iter != controllers.end() ) {

		delete iter->second;
		iter++;
	}

	controllers.clear();
}

//------------------------------------------------------------------------------
void createScene() {

	TTriMesh *mesh;

	if( TColladaTriMeshLoader().loadFromFile("./scene/Stage01k.DAE", mesh) ) {

		scene.addObject(
			"triMesh",
			new SceneObject(new TTriMeshRenderable(mesh),
			new TFixedPipelineShader(texMgr.loadTexture("./texture/cube.bmp")) ),
			NORMAL_VIS_FLAG);

		static_cast<TFixedPipelineShader*>(
			scene.getObjectByName("triMesh")->getShader() )->setUseTexture(false);
	};
}

//------------------------------------------------------------------------------
void initObjectTransforms() {

	camera.moveAbs( vec3(0.0f, 40.0f, 70.0f) );
	camera.buildReflectMtx( vec4(0.0f, 1.0f, 0.0f, 0.0f) );
	camera.setProjectionMode(TCamera::PERSPECTIVE);
}

//------------------------------------------------------------------------------
void resetObjectTransforms() {

	Scene::TObjectIterator iter = scene.objectIterator();

	while( iter.hasNext() ) {
		iter.getNext()->resetXForm();
	}

	camera.resetXForm();
}

//------------------------------------------------------------------------------
int main(int argc, char** argv) {

	for(int i=0; i<argc; ++i) {

		printf("%s\n", argv[i]);
	}

	glutInitWindowSize(600, 600);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInit(&argc, argv);

	glutCreateWindow("Collada Test");
	glutReshapeFunc(reshape);
	glutDisplayFunc(display);
	glutKeyboardFunc(keyboard);
	glutIdleFunc(idle);

	createScene();
	initObjectTransforms();
	initControllers();

	glutMainLoop();

	deinitControllers();

	return 0;
}

//------------------------------------------------------------------------------
static void drawBasis(float halfSize)
{
	glPushMatrix();

	glDisable(GL_DEPTH_TEST);
	glDisable(GL_TEXTURE_2D);
	glDisable(GL_LIGHTING);

	glBegin(GL_LINES);

	glColor3fv( vec3(1.0f, 0.0f, 0.0f).getData() );
	glVertex3fv( vec3(halfSize, 0.0f, 0.0f).getData() );
	glVertex3fv( vec3(0.0f, 0.0f, 0.0f).getData() );

	glColor3fv( vec3(0.0f, 1.0f, 0.0f).getData() );
	glVertex3fv( vec3(0.0f, halfSize, 0.0f).getData() );
	glVertex3fv( vec3(0.0f, 0.0f, 0.0f).getData() );

	glColor3fv( vec3(0.0f, 0.0f, 1.0f).getData() );
	glVertex3fv( vec3(0.0f, 0.0f, halfSize).getData() );
	glVertex3fv( vec3(0.0f, 0.0f, 0.0f).getData() );

	glEnd();

	glPopMatrix();
}

//------------------------------------------------------------------------------
static void reshape(int width, int height)
{
	glViewport(0, 0, width, height);

	camera.setViewParam(deg2Rad(45.0f), (float)width / (float)height, 1.0f, 500.0f);
}

//------------------------------------------------------------------------------
static void idle(void)
{
	static DWORD lastTick = 0;
	DWORD currentTick = GetTickCount();

	if(currentTick - lastTick > 10) {

		float dt = (currentTick - lastTick) * 0.001f;

		scene.step(dt);

		TControllerList::iterator iter = controllers.begin();

		while( iter != controllers.end() ) {

			iter->second->step(dt, iter->first);
			iter++;
		}

		lastTick = currentTick;
	}


	glutPostRedisplay();
}

//------------------------------------------------------------------------------
static void display(void)
{
	glClearColor(0.05, 0.1, 0.2, 0.0);
	glClearDepth(1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	camera.setReflectMtxEnabled(false);

	glFrontFace(GL_CW);

	scene.beginRender(&camera);

	scene.render(0xffffffff);
	
	drawBasis(10.0f);

	scene.endRender();

	

	glFlush();
	glutSwapBuffers();
}

//------------------------------------------------------------------------------
static void keyboard(uint8 c, int x, int y)
{
	switch (c)
	{
	case 27: case 'q':
		deinitControllers();
		exit(0);
		break;

	default:
		//printf("%d is pressed\n", c);
		break;
	}
}

//------------------------------------------------------------------------------
*/